
let fs = require("fs");
let types = require("babel-types");
let parser = require("@babel/parser");
let traverse = require("@babel/traverse").default;
let generator = require("@babel/generator").default;
const path = require("path");
//根目录就是当前的工作目录
let baseDir = toUnixPath(process.cwd()); // \ => /
function toUnixPath(filePath) {
  return filePath.replace(/\\/g, "/");
}

class Complication {
    constructor(options) {
        this.options = options
        this.modules = [] //存放编译生产的所有模块
        this.chunks = []; //代码块的数组
        this.entrypoints = new Map(); //入口的数组
        this.assets = {}; //产出的资源
        this.files = [];
    }
    //这个才是编译最核心的方法
    build(callback) {
        //5.根据配置中的entry找出入口文件
        let entry = {}
        if (typeof this.options.entry === "string") {
            entry.main = this.options.entry;
        } else {
            entry = this.options.entry;
        }
        let context = toUnixPath(process.cwd()) // /Users/adminx/Desktop/Ikki-study/webpack-advanced/webpack5-base/4.flow
        for (let entryName in entry) {
            //找到入口文件的绝对路径
            let entryFilePath = path.posix.join(context, entry[entryName]); // /Users/adminx/Desktop/Ikki-study/webpack-advanced/webpack5-base/4.flow/src/entry1.js
            //6.从入口文件出发,调用所有配置的Loader对模块进行编译
            let entryModule = this.buildModule(entryName, entryFilePath);
            // console.log('entryModule',entryModule)
            //8.根据入口和模块之间的依赖关系，组装成一个个包含多个模块的 Chunk
            let chunk = {
                name: entryName, //代码块的名字就是入口的名字
                entryModule, //入口模块 entry1.js
                modules: this.modules.filter((item) => item.name === entryName),
            };
            this.chunks.push(chunk);
            this.entrypoints.set(entryName, { name: entryName, chunks: [entryName] });
            //9.再把每个Chunk转换成一个单独的文件加入到输出列表
            this.chunks.forEach((chunk) => {
                let filename = this.options.output.filename.replace("[name]", chunk.name);
                this.assets[filename] = getSource(chunk);
            });
            this.files = Object.keys(this.assets)
        }
        callback(null, {
            entrypoints: this.entrypoints,
            chunks: this.chunks,
            modules: this.modules,
            files: this.files,
            assets: this.assets,
        });
    }
    // loader如何工作的 
    // 1.读取源代码 let name= 'entry1'; 
    // 2.把源代码传递给最后一个loader

    //name此模块是属于哪个入口的 modulePath 模块的绝对路径
    buildModule(name, modulePath){
        //6.从入口文件出发,调用所有配置的Loader对模块进行编译
        let sourceCode = fs.readFileSync(modulePath, "utf8"); //读取模块的内容
        let { rules } = this.options.module; //拿到所有规则
        let loaders = []; //
        rules.forEach(rule => {
            let { test } = rule
            if(modulePath.match(test)){ //模式匹配
                loaders.push(...rule.use); 
            }
        });
        sourceCode = loaders.reduceRight((sourceCode,loader)=>{
            return require(loader)(sourceCode)
        },sourceCode)

        //当前模块的模块ID
        let moduleId = "./" + path.posix.relative(baseDir, modulePath); //./src/entry1.js   ./src/entry2.js
        let module = { id: moduleId, dependencies: [], name };

        //7.再找出该模块依赖的模块，再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
        let ast = parser.parse(sourceCode, { sourceType: "module" });
        traverse(ast,{
            CallExpression:({node})=>{
                if(node.callee.name === 'require'){
                    //获取依赖模块的相对路径 wepback打包后不管什么模块，模块ID都是相对于根目录的相对路径 ./src ./node_modules
                    let depModuleName = node.arguments[0].value // ./title
                    // 获取当前模块的所在的目录
                    let dirname = path.posix.dirname(modulePath) // /Users/adminx/Desktop/Ikki-study/webpack-advanced/webpack5-base/4.flow/src
                    // 生成绝对路径
                    let depModulePath = path.posix.join(dirname, depModuleName); // /Users/adminx/Desktop/Ikki-study/webpack-advanced/webpack5-base/4.flow/src/title
                    let extensions = this.options.resolve.extensions; // 获取后缀名数组  [ 'js', 'jsx', 'ts', 'tsx', 'json' ]
                    depModulePath = tryExtensions(depModulePath, extensions); // /Users/adminx/Desktop/Ikki-study/webpack-advanced/webpack5-base/4.flow/src/title.js
                    //生成此模块的模块ID
                    let depModuleId = "./" + path.posix.relative(baseDir, depModulePath);  //  ./src/title.js
                    // 改造语法树
                    node.arguments = [types.stringLiteral(depModuleId)]; // ./title => ./src/title.js
                    //把此模块依赖的模块ID和模块路径放到此模块的依赖数组中
                    module.dependencies.push({ depModuleId, depModulePath });
                }
            }
        })
        let { code } = generator(ast); //根据改造后的语法树生成源代码
        module._source = code // module._source指向次模块改造后的源码

        //7.再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
        module.dependencies.forEach(({depModuleId, depModulePath})=>{
            let depModule = this.buildModule(name, depModulePath);
            this.modules.push(depModule);
        })
        return module;

    }
}
function tryExtensions(modulePath, extensions) {
    if (fs.existsSync(modulePath)) {
      return modulePath;
    }
    for (let i = 0; i < extensions.length; i++) {
      let filePath = modulePath + '.' +extensions[i];
      if (fs.existsSync(filePath)) {
        return filePath;
      }
    }
    throw new Error(`${modulePath}没找到`);
}

function getSource(chunk) {
    return `
     (() => {
      var modules = {
        ${chunk.modules.map(
          (module) => `
          "${module.id}": (module) => {
            ${module._source}
          },
        `
        )}  
      };
      var cache = {};
      function require(moduleId) {
        var cachedModule = cache[moduleId];
        if (cachedModule !== undefined) {
          return cachedModule.exports;
        }
        var module = (cache[moduleId] = {
          exports: {},
        });
        modules[moduleId](module, module.exports, require);
        return module.exports;
      }
      var exports ={};
      ${chunk.entryModule._source}
    })();
    
     `;
  }
module.exports = Complication